/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cocma;

import schedulingScheme.*;
import java.util.ArrayList;
import java.util.BitSet; //for performance reasons
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
import helper.Config;
import schedulingScheme.GeneticOperations;
import schedulingScheme.Helper;
import schedulingScheme.POI;
import schedulingScheme.SensorNode;
import schedulingScheme.SensorsController;
import sun.management.Sensor;



/**
 *
 * @author Andrzej
 */
public class Cocma implements SensorsController {

    Random random = Config.getSalt();
    CoverageMatrix R;
//    int chromosomelength = Config.getSensorNumber();
    int populationSize = 100;
    double crossoverRate = 0.5;
    double mutationRate = 0.07;
    private Chromosome bestSolution;
    protected List<SensorNode> nodes;
    List<POI> pois;
    FitnessValue fv;

    public Cocma(List<SensorNode> nodes, List<POI> pois) {
        this.nodes = nodes;
        this.pois = pois;
    }


    String chromosomeToString(boolean[] b) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < b.length; i++) {
            sb.append(b[i] ? "1" : "0");
        }
        return sb.toString();
    }

    public List<SensorNode> initializeNodes() {
        R = new CoverageMatrix(nodes, pois);
        List<Chromosome> population = new ArrayList<Chromosome>(populationSize);

        //initial population
        for (int i = 0; i < populationSize; i++) {
            population.add(produceRandomChromosome(nodes.size()));
        }



        fv = new FitnessValue(R);
        bestSolution = population.get(0);//random chromosome is best
        int bestSolutionUnchanged = 0;

        for (int k = 0, j = 0; k < 100 && bestSolutionUnchanged < 5; k++) {

            Collections.sort(population, fv);
            System.out.println("best solution:" + population.get(population.size() - 1).toString() + " fitness value:" + fv.fitnessValue(population.get(population.size() - 1)));
            if (fv.fitnessValue(population.get(population.size() - 1)) > fv.fitnessValue(bestSolution)){
                bestSolution = population.get(population.size() - 1);
                bestSolutionUnchanged = 0;
            }else
                bestSolutionUnchanged++;
            

            List<Chromosome> newPopulation = new ArrayList<Chromosome>(populationSize);

            for (int i = 0; i < populationSize / 2; i++) {
                Chromosome parent1 = GeneticOperations.tournament(population, fv, 10, 0.75);
                Chromosome parent2 = GeneticOperations.tournament(population, fv, 10, 0.75);
                
                List<Chromosome> newChromosomes;
                if (random.nextDouble() < crossoverRate) {
                    newChromosomes = parent1.singlePointCrossover(parent2);
                    
                } else {
                    newChromosomes = new ArrayList<Chromosome>(2);
                    newChromosomes.add(parent1);
                    newChromosomes.add(parent2);
                }
                for (Chromosome c: newChromosomes){
                    if (random.nextDouble() < mutationRate)
                        c.mutatate();
                }
                newPopulation.addAll(newChromosomes);

            }


            //local search
            for (Chromosome chromosome: newPopulation){
                for (int i=0;i<chromosome.length();i++){
                    if (chromosome.getAllele(i)){
                        double v1 = fv.fitnessValue(chromosome);
                        chromosome.setAllele(i, false);
                        double v2 = fv.fitnessValue(chromosome);
                        if (v1 > v2)
                            chromosome.setAllele(i, true);
                    }
                }
            }

            population = newPopulation;
        }
        ArrayList<SensorNode> res = new ArrayList<SensorNode>();

        for (int i=0; i < nodes.size();i++){
            if (bestSolution.getAllele(i))
                res.add(nodes.get(i));
        }
        return res;

    }




    private Chromosome produceRandomChromosome(int chromosomelength) {
        Chromosome chromosome = new Chromosome(chromosomelength);
//        double p = random.nextDouble();
        for (int i = 0; i < chromosomelength; i++) {
            chromosome.setAllele(i, random.nextDouble() < 0.9);
        }
        return chromosome;
    }




    /**
     * as in CoCmA article
     * @param s
     * @return
     */
    @Override
    public List<SensorNode> replaceDiedSensor(SensorNode s) {

        int index = nodes.indexOf(s);
        BitSet coverageOfDiedSensor = R.coverageOfSensor(index);
        BitSet zeros = new BitSet(coverageOfDiedSensor.length());


        BitSet uncoveredPOIs;
        BitSet tmp1;
        BitSet coveredPOIsWithoutDiedSensor = new BitSet(coverageOfDiedSensor.length());

        for (int i=0; i< nodes.size();i++){
            if (nodes.get(i).isOn())
                coveredPOIsWithoutDiedSensor.or(R.coverageOfSensor(i));
        }
        tmp1  = ((BitSet)coveredPOIsWithoutDiedSensor.clone());
        tmp1.or(coverageOfDiedSensor);
        tmp1.xor(coveredPOIsWithoutDiedSensor);
        uncoveredPOIs = tmp1;
        assert(coverageOfDiedSensor.cardinality() >= uncoveredPOIs.cardinality()):"" + coverageOfDiedSensor.cardinality() + " < " + uncoveredPOIs.cardinality();

        List<SensorNode> neighbors = new ArrayList<SensorNode>();
        System.out.println(uncoveredPOIs);
        for (int i = 0; i < nodes.size(); i++) {
            if (nodes.get(i).isOn() == false && nodes.get(i).getBattery() > 0) {
                BitSet tmp = (BitSet) uncoveredPOIs.clone();
                tmp.and(R.coverageOfSensor(i));
                if (tmp.cardinality() > 0) {
                    neighbors.add(nodes.get(i));
                }
            }
        }
        List<List<SensorNode>> subsets = Helper.getAllSubsets(neighbors);
        List<SensorNode> bestSubset = Collections.EMPTY_LIST;
        double bestValue = Double.MIN_VALUE;
        for (List<SensorNode> subset : subsets){
            BitSet tmp = new BitSet(coverageOfDiedSensor.length());
            for (SensorNode sensor : subset) {
                tmp.or(R.coverageOfSensor(nodes.indexOf(sensor)));
            }
            tmp.and(uncoveredPOIs);
            double value = tmp.cardinality() + 1/(1 + subset.size());
            if (value > bestValue){
                bestSubset = subset;
                bestValue = value;
            }

        }
        for (SensorNode sensor: bestSubset){
            sensor.setOn(true);
        }
        return bestSubset;
    }

    public boolean reactToTimePassed(int time) {
        return false;
    }

    public List<SensorNode> roundPassed(int roundNumber) {
        return null;
    }
}






